/* Created on Sep 29, 2010 */
package org.tdwg.codeblitz.service;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.ws.Response;

import org.tdwg.codeblitz.model.MediaFile;
import org.tdwg.codeblitz.model.Point;
import org.tdwg.codeblitz.model.Walk;

import com.google.gdata.client.GoogleService;
import com.google.gdata.client.Service.GDataRequest;
import com.google.gdata.client.Service.GDataRequest.RequestType;
import com.google.gdata.util.ContentType;
import com.google.gdata.util.ServiceException;

@Path("/gpx")
public class GPXService {
	private static final String SERVICE_URL = "http://tables.googlelabs.com/api/query";

	private static final Logger LOG = Logger.getLogger(GPXService.class
			.getCanonicalName());

	private final GoogleService service = new GoogleService("fusiontables",
			"org.tdwg.codeblitz.GPSVoiceTracker");
	private final String STORAGE_DIRECTORY = "/tmp/";
	private static SimpleDateFormat GPX_DATE_FORMAT = new SimpleDateFormat(
			"yyyy-MM-dd'T'HH:mm:ss'Z'");
	private static SimpleDateFormat PARSABLE_GPX_DATE_FORMAT = new SimpleDateFormat(
			"yyyy-MM-dd HH:mm:ss");

	@PUT
	@Produces("text/plain")
	@Path("/file/update")
	public String updateFile(@QueryParam("rowid") String rowid,
			@QueryParam("host") String fileHost,
			@QueryParam("specimen") String fileSpecimen,
			@QueryParam("comments") String fileComments) {

		try {
			service.setUserToken("DQAAALoAAABQ86XMwg66GlZKT11AYGR2XbUC3a--fo3-M2JDmpjxRjbc-i83jKldxWp_nnkrvyIyjROpPk9uPvxVlq2o_rsfv7xLcjkNIM2eOMKxhmDmSmq_Z0BZ2yR3yTYZgqKF-sqtPVak0tKwvJVfdw5r_M1ImQPYgDo5fQDPJ-yqlGsgMKMMyFHuNPuRaA8_80Polio2DFBNeXi7u0D80TMmfU1tsHJa3TU6Wbtn4q95g8ObeIqhrKS3mSsDnJYFCmQxOEM");
			String update_sql = "UPDATE " + MediaFile.TABLE_ID
					+ " SET Host = '" + fileHost + "', Specimen = '"
					+ fileSpecimen + "', " + "Comments = '" + fileComments
					+ "' WHERE ROWID = '" + rowid + "'";

			runUpdate(update_sql);
		} catch (Exception e) {
			LOG.log(Level.SEVERE, "An error occurred when updating file "
					+ rowid, e);
			throw new RuntimeException("The update failed, see log for details");
		}

		return "ok";
	}

	@POST
	@Consumes(MediaType.MULTIPART_FORM_DATA)
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/upload")
	public Response uploadGPX(
			@FormParam("GPX") InputStream inputStreamGPX,
			@FormParam("AMRZIP") InputStream inputStreamAMRZIP,
			@FormParam("WalkDescription") @DefaultValue("") String walkDescription,
			@FormParam("WalkName") @DefaultValue("") String walkName,
			@FormParam("WalkerName") @DefaultValue("") String walkerName) {
		service.setUserToken("DQAAALoAAABQ86XMwg66GlZKT11AYGR2XbUC3a--fo3-M2JDmpjxRjbc-i83jKldxWp_nnkrvyIyjROpPk9uPvxVlq2o_rsfv7xLcjkNIM2eOMKxhmDmSmq_Z0BZ2yR3yTYZgqKF-sqtPVak0tKwvJVfdw5r_M1ImQPYgDo5fQDPJ-yqlGsgMKMMyFHuNPuRaA8_80Polio2DFBNeXi7u0D80TMmfU1tsHJa3TU6Wbtn4q95g8ObeIqhrKS3mSsDnJYFCmQxOEM");
		String walkUniqueID = Long.toString(System.currentTimeMillis());

		try {
			XMLStreamReader reader = XMLInputFactory.newInstance()
					.createXMLStreamReader(inputStreamGPX);

			String walkDate = null;
			Double latitude = 0.0d;
			Double longitude = 0.0d;
			Double elevation = 0.0d;
			String date = "";

			String name;
			StringBuffer bulk_insert = new StringBuffer();
			int inserted = 1;

			while (reader.hasNext()) {
				reader.next();
				if (reader.getEventType() == XMLStreamReader.START_ELEMENT) {
					name = reader.getLocalName();

					if (name.equals("trkpt")) {
						latitude = Double.parseDouble(reader
								.getAttributeValue(0));
						longitude = Double.parseDouble(reader
								.getAttributeValue(1));
					} else if (name.equals("ele")) {
						try {
							elevation = Double.parseDouble(reader
									.getElementText());
						} catch (NumberFormatException ne) {
							elevation = 0d;
						}
					} else if (name.equals("time")) {
						date = reader.getElementText();
						if (walkDate == null) {
							walkDate = date; // get the "first" timestamp for
												// the walk date
						}
					}
				} else if (reader.getEventType() == XMLStreamReader.END_ELEMENT) {
					name = reader.getLocalName();
					if (reader.getLocalName().equals("trkpt")) {

						bulk_insert
								.append("insert into "
										+ Point.TABLE_ID
										+ " ('GUID', 'Date', 'Elevation', 'Location', 'Walk_REF') values ('");
						bulk_insert.append(System.currentTimeMillis());
						bulk_insert.append("', '");
						bulk_insert.append(date);
						bulk_insert.append("', '");
						bulk_insert.append(elevation);
						bulk_insert.append("', '");
						bulk_insert.append(latitude);
						bulk_insert.append(" ");
						bulk_insert.append(longitude);
						bulk_insert.append("', '");
						bulk_insert.append(walkUniqueID);
						bulk_insert.append("');\n");
						inserted++;

						if (inserted % 100 == 0) {
							runUpdate(bulk_insert.toString());
							bulk_insert = new StringBuffer();
							inserted = 1;
						}
					}

					latitude = 0.0d;
					longitude = 0.0d;
					elevation = 0.0d;
					date = "";
				}
			}

			if (!bulk_insert.toString().trim().equals("")) {
				runUpdate(bulk_insert.toString()); // insert any remaining
			}

			runUpdate("insert into "
					+ Walk.TABLE_ID
					+ " ('GUID', 'Date', 'Description', 'Name', 'Walker') values ('"
					+ walkUniqueID + "', '" + walkDate + "', '"
					+ walkDescription + "', '" + walkName + "', '" + walkerName
					+ "')");

		} catch (Exception e) {
			LOG.log(Level.SEVERE, "Could not parse the GPX file", e);
			RuntimeException re = new RuntimeException(
					"An error occurred parsing the GPX file");
			re.initCause(e);
			throw re;
		}

		List<Point> walkPoints = new QueryService()
				.queryPoinsForWalk(walkUniqueID);
		File storageDirectory = new File(STORAGE_DIRECTORY + walkUniqueID);
		storageDirectory.mkdir();
		try {
			File outputFile = new File(storageDirectory.getPath() + "/"
					+ Long.toString(System.currentTimeMillis()) + ".zip");
			FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
			byte[] buf = new byte[1024];
			int len;
			int total_read = 0;
			while ((len = inputStreamAMRZIP.read(buf)) > 0) {
				fileOutputStream.write(buf, 0, len);
				total_read += len;
			}

			if (total_read > 0) {
				inputStreamAMRZIP.close();
				fileOutputStream.close();
				ZipFile file = new ZipFile(outputFile);
				Enumeration entries = file.entries();
				StringBuffer bulk_insert = new StringBuffer();
				int inserted = 1;
				while (entries.hasMoreElements()) {

					ZipEntry entry = (ZipEntry) entries.nextElement();
					String name = entry.getName();
					/*
					 * currently, because we had to convert some field data and
					 * needed to maintain the file dates, the zip file contains
					 * both the AMR and MP3 file. For actual production ready
					 * software, the zip file would be assumed to only contain
					 * the MP3 file. In the long term if dates become an issue,
					 * a manifest file of some kind could be supplied in the zip
					 * file.
					 * 
					 * To that end, it might also make sense to assume the GPX
					 * file is included in the cip file with the MP3s and is
					 * called something like WALK.GPX
					 */
					if (name.toUpperCase().endsWith("AMR")
							|| name.toUpperCase().endsWith("MP3")) {
						String[] nameParts = name.split("/");
						String baseName = "";
						String fullPath = null;
						if (nameParts.length > 1) {
							baseName = nameParts[1];
							fullPath = storageDirectory.getPath() + "/"
									+ baseName;
							outputFile = new File(fullPath);
							try {
								InputStream inputStream = file
										.getInputStream(entry);
								fileOutputStream = new FileOutputStream(
										outputFile);
								buf = new byte[1024];
								while ((len = inputStream.read(buf)) > 0) {
									fileOutputStream.write(buf, 0, len);
								}
								inputStream.close();
								fileOutputStream.close();
							} catch (FileNotFoundException e) {
								e.printStackTrace();
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
						if (name.toUpperCase().endsWith("AMR")) {
							Point nearestPoint = null;
							Date fileDate = new Date(entry.getTime());
							Date previousDate = null;
							for (int i = 0; i < walkPoints.size(); i++) {
								Point point = walkPoints.get(i);
								String dateString = point.getDate()
										.replaceAll("T", " ")
										.replaceAll("Z", "");
								Date currentDate = null;
								try {
									currentDate = PARSABLE_GPX_DATE_FORMAT
											.parse(dateString);
									if (null == previousDate
											&& fileDate.after(currentDate)) {
										nearestPoint = point;
										// System.out.println(fileDate +
										// " is after " + currentDate);
										break;
									} else if (null != previousDate
											&& fileDate.before(previousDate)
											&& fileDate.after(currentDate)) {
										nearestPoint = point;
										// System.out.println(fileDate +
										// " is between " + previousDate +
										// " and " + currentDate);
										break;
									}
								} catch (ParseException e) {
									e.printStackTrace();
								}
								previousDate = currentDate;

							}
							if (null == nearestPoint) {
								nearestPoint = walkPoints
										.get(walkPoints.size() - 1);
								// System.out.println(fileDate + " is before " +
								// previousDate);
							}
							bulk_insert
									.append("insert into 264861 ('GUID', 'Date', 'Location', 'URI', 'Walk_REF') values ('");
							bulk_insert.append(System.currentTimeMillis());
							bulk_insert.append("', '");
							bulk_insert
									.append(GPX_DATE_FORMAT.format(fileDate));
							bulk_insert.append("', '");
							bulk_insert.append(nearestPoint.getLat() + " "
									+ nearestPoint.getLon());
							bulk_insert.append("', '");
							bulk_insert.append(fullPath
									.replaceAll("amr", "mp3"));
							bulk_insert.append("', '");
							bulk_insert.append(walkUniqueID);
							bulk_insert.append("');\n");
							inserted++;

							if (inserted % 100 == 0) {
								runUpdate(bulk_insert.toString());
								bulk_insert = new StringBuffer();
								inserted = 1;
							}
						}
					}
				}

				if (!bulk_insert.toString().trim().equals("")) {
					runUpdate(bulk_insert.toString()); // insert any remaining
				}
			} else { // make sure not to leak resources
				inputStreamAMRZIP.close();
				fileOutputStream.close();
			}

		} catch (Exception e) {
			LOG.log(Level.SEVERE, "Could not parse the ZIP file", e);
			RuntimeException re = new RuntimeException(
					"An error occurred parsing the ZIP file");
			re.initCause(e);
			throw re;
		}

		return null;
	}

	private void runUpdate(String updateQuery) throws IOException,
			ServiceException {
		URL url = new URL(SERVICE_URL);
		GDataRequest request = service.getRequestFactory().getRequest(
				RequestType.INSERT, url,
				new ContentType("application/x-www-form-urlencoded"));
		OutputStreamWriter writer = new OutputStreamWriter(
				request.getRequestStream());
		writer.append("sql=" + URLEncoder.encode(updateQuery, "UTF-8"));
		writer.flush();

		request.execute();
	}
}